`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2021/04/05 18:01:55
// Design Name: 
// Module Name: I2cMaster_BitCtr
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////
`include "i2c_master_define.v"

module I2cMaster_BitCtr(
    input                                   sys_clk,     //system clock 
    input                                   sys_rst_n,   //asynchronous active low reset 
    input                                   sys_rst,     //synchronous active high reset  
    input                                   core_en,     //core enable signal   
    
    input  wire [15:0]                      clk_cnt,    // clock prescale value                              

    input  wire [3:0]                       cmd,        // command from byte controller
    output reg                              cmd_ack,    // bit state finish command complete acknowledge
    output reg                              busy,       // i2c bus busy
    output reg                              al,         // i2c bus arbitration lost
    
    input                                   din,
    output reg                              dout,
    
    // I2C signals
    // i2c clock line
    input                                   scl_i,      // i2c clock line input
    output                                  scl_o,      // i2c clock line output
    output reg                              scl_oen,    // i2c clock line output enable (active low)   
    // i2c data line
    input                                   sda_i,      // i2c data line input
    output                                  sda_o,      // i2c data line output
    output reg                              sda_oen    // i2c data line output enable (active low)
    );    

//*************************1.capture slave or another Master input SDA and SCL*****************************************//
//******************2.capture local Master output SDA and SCL for determine I2c bus state*******************************//
// reduce metastability risk
reg [1:0]   cSCL,cSDA;      //cSCL(capture SCL),cSDA(capture SDA)
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)   
        begin                   //asynchronous active low reset
            cSCL <= #1 2'b00;         
            cSDA <= #1 2'b00;             
        end
    else if(sys_rst)  
        begin                   //synchronous active low high
            cSCL <= #1 2'b00;         
            cSDA <= #1 2'b00;             
        end        
    else   
        begin
            cSCL <= {cSCL[0],scl_i};  
            cSDA <= {cSDA[0],sda_i};      
        end

// filter SCL and SDA signals; (attempt to) remove glitches
reg [13:0] filter_cnt;      // clock divider for filter
always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)                          
        filter_cnt <= 14'h0;
    else if(sys_rst||!core_en)              
        filter_cnt <= 14'h0;
    else if(~|filter_cnt)                   
        filter_cnt <= clk_cnt >> 2; //16x I2C bus frequency
    else                                    
        filter_cnt <= filter_cnt - 1'b1;

reg [2:0] fSCL, fSDA;      // SCL and SDA filter inputs        
always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)        
        begin
            fSCL <= 3'b111;                 
            fSDA <= 3'b111;                 
        end       
    else if (sys_rst)    
        begin
            fSCL <= 3'b111;                 
            fSDA <= 3'b111;                 
        end  
    else if (~|filter_cnt)  
        begin
            fSCL <= {fSCL[1:0],cSCL[1]};    
            fSDA <= {fSDA[1:0],cSDA[1]};    
        end 
       
//generate filtered SCL and SDA signals
reg        sSCL, sSDA;      // filtered and synchronized SCL and SDA inputs
reg        dSCL, dSDA;      // delayed versions of sSCL and sSDA
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)      
        begin
            sSCL <= #1 1'b1;                
            sSDA <= #1 1'b1;
            dSCL <= #1 1'b1;                
            dSDA <= #1 1'b1;              
        end
    else if (sys_rst)   
        begin
            sSCL <= #1 1'b1;                
            sSDA <= #1 1'b1;
            dSCL <= #1 1'b1;                
            dSDA <= #1 1'b1;              
        end
    else                
        begin
            sSCL <= #1 &fSCL[2:1] | &fSCL[1:0] | (fSCL[2] & fSCL[0]);
            sSDA <= #1 &fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0]);
            dSCL <= #1 sSCL;                
            dSDA <= #1 sSDA;              
        end

//***************whenever the slave is not ready it can delay the cycle by pulling SCL low********************//
// delay scl_oen means through one-beat and reduce metastability risk
reg    dscl_oen;
always @(posedge sys_clk)
    dscl_oen <= #1 scl_oen;
    
// slave_wait is asserted when master wants to drive SCL high, but the slave pulls it low
// slave_wait remains asserted until the slave releases SCL
reg    slave_wait;
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)  
        slave_wait <= 1'b0;
    else            
        slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) | (slave_wait & ~sSCL);
        //scl_oen & ~dscl_oen respect scl_oen fall edge
    
// master drives SCL high, but another master pulls it low
// master start counting down its low cycle now (clock synchronization)
wire scl_sync   = dSCL & ~sSCL & scl_oen;
    
// generate clk enable signal
reg [15:0] cnt;             // clock divider counter (synthesis)
reg        clk_en;          // clock generation signals
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)   
        begin
            cnt <= #1 16'h0;        
            clk_en <= #1 1'b1;      
        end
    else if (sys_rst || ~|cnt || !core_en || scl_sync)  
        begin
            cnt <= #1 clk_cnt;      
            clk_en <= #1 1'b1;      
        end
    else if (slave_wait)    
        begin
            cnt <= #1 cnt;          
            clk_en <= #1 1'b0;      
        end
    else    
        begin
            cnt <= #1 cnt - 16'h1;  
            clk_en <= #1 1'b0;      
        end 

//*************************through capture SCL and SDA determine I2c bus state************************************// 
// 1) detect i2c bus busy signal
// 2) detect i2c arbitration lost signal       
// detect start condition => detect falling edge on SDA while SCL is high
// detect stop condition => detect rising edge on SDA while SCL is high
reg  sta_condition,sto_condition;
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)      
        begin
            sta_condition <= #1 1'b0;                   
            sto_condition <= #1 1'b0;                   
        end
    else if (sys_rst)   
        begin
            sta_condition <= #1 1'b0;                   
            sto_condition <= #1 1'b0;                   
        end
    else                
        begin
            sta_condition <= #1 ~sSDA & dSDA & sSCL;    
            sto_condition <= #1 sSDA & ~dSDA & sSCL;    
        end
       
//Detect I2c bus busy state => generate i2c bus busy signal
always @(posedge sys_clk or negedge sys_rst_n)
    if(!sys_rst_n)                       
        busy <= #1 1'b0;
    else if (sys_rst)                   
        busy <= #1 1'b0;
    else                                
        busy <= #1 (sta_condition | busy) & ~sto_condition;   //means from start to stop the busy signal is high,otherwise is low
        
// generate arbitration lost signal
// aribitration lost when:
// 1) master drives SDA high, but the i2c bus is low
// 2) stop detected while not requested
reg cmd_stop;
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)                  
        cmd_stop <= #1 1'b0;
    else if (sys_rst)               
        cmd_stop <= #1 1'b0;
    else if (clk_en)                
        cmd_stop <= #1 `I2C_CMD_STOP;   //cmd_stop is always is 0?  by jt
 
reg[17:0]  c_state; 
reg        sda_chk;         // check SDA output (Multi-master arbitration)         
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)
        al <= #1 1'b0;
    else if (sys_rst)
        al <= #1 1'b0;
    else
        al <= #1 (sda_chk & ~sSDA & sda_oen) | (|c_state & sto_condition & ~cmd_stop);
        
// generate dout signal (store SDA on rising edge of SCL)
always @(posedge sys_clk)
      if (sSCL & ~dSCL) dout <= #1 sSDA;
         
//********************************generate statemachine******************************************//
// nxt_state decoder
parameter [17:0] idle    = 18'b0_0000_0000_0000_0000;
parameter [17:0] start_a = 18'b0_0000_0000_0000_0001;
parameter [17:0] start_b = 18'b0_0000_0000_0000_0010;
parameter [17:0] start_c = 18'b0_0000_0000_0000_0100;
parameter [17:0] start_d = 18'b0_0000_0000_0000_1000;
parameter [17:0] start_e = 18'b0_0000_0000_0001_0000;
parameter [17:0] stop_a  = 18'b0_0000_0000_0010_0000;
parameter [17:0] stop_b  = 18'b0_0000_0000_0100_0000;
parameter [17:0] stop_c  = 18'b0_0000_0000_1000_0000;
parameter [17:0] stop_d  = 18'b0_0000_0001_0000_0000;
parameter [17:0] rd_a    = 18'b0_0000_0010_0000_0000;
parameter [17:0] rd_b    = 18'b0_0000_0100_0000_0000;
parameter [17:0] rd_c    = 18'b0_0000_1000_0000_0000;
parameter [17:0] rd_d    = 18'b0_0001_0000_0000_0000;
parameter [17:0] wr_a    = 18'b0_0010_0000_0000_0000;
parameter [17:0] wr_b    = 18'b0_0100_0000_0000_0000;
parameter [17:0] wr_c    = 18'b0_1000_0000_0000_0000;
parameter [17:0] wr_d    = 18'b1_0000_0000_0000_0000;
    
always @(posedge sys_clk or negedge sys_rst_n)
    if (!sys_rst_n)     
        begin
            c_state <= #1 idle;     
            cmd_ack <= #1 1'b0;     
            scl_oen <= #1 1'b1;     
            sda_oen <= #1 1'b1;     
            sda_chk <= #1 1'b0;     
        end
    else if (sys_rst | al)  
        begin
            c_state <= #1 idle;     
            cmd_ack <= #1 1'b0;     
            scl_oen <= #1 1'b1;     
            sda_oen <= #1 1'b1;     
            sda_chk <= #1 1'b0;     
        end
    else  
        begin
            cmd_ack   <= #1 1'b0; // default no command acknowledge + assert cmd_ack only 1clk cycle    
            if (clk_en)
                case (c_state) // synopsys full_case parallel_case
                // idle state
                idle:    
                    begin
                        case (cmd) // synopsys full_case parallel_case
                            `I2C_CMD_START: c_state <= #1 start_a;
                            `I2C_CMD_STOP:  c_state <= #1 stop_a;
                            `I2C_CMD_WRITE: c_state <= #1 wr_a;
                            `I2C_CMD_READ:  c_state <= #1 rd_a;
                            default:        c_state <= #1 idle;
                        endcase    
                        scl_oen <= #1 scl_oen; // keep SCL in same state
                        sda_oen <= #1 sda_oen; // keep SDA in same state
                        sda_chk <= #1 1'b0;    // don't check SDA output
                    end    
                 // start
                 start_a:
                    begin
                        c_state <= #1 start_b;
                        scl_oen <= #1 scl_oen; // keep SCL in same state
                        sda_oen <= #1 1'b1;    // set SDA high
                        sda_chk <= #1 1'b0;    // don't check SDA output
                    end
                 start_b:
                    begin
                        c_state <= #1 start_c;
                        scl_oen <= #1 1'b1; // set SCL high
                        sda_oen <= #1 1'b1; // keep SDA high
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 start_c:
                    begin
                        c_state <= #1 start_d;
                        scl_oen <= #1 1'b1; // keep SCL high
                        sda_oen <= #1 1'b0; // set SDA low
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 start_d:
                    begin
                        c_state <= #1 start_e;
                        scl_oen <= #1 1'b1; // keep SCL high
                        sda_oen <= #1 1'b0; // keep SDA low
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 start_e:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0; // set SCL low
                        sda_oen <= #1 1'b0; // keep SDA low
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
    
                 // stop
                 stop_a:
                    begin
                        c_state <= #1 stop_b;
                        scl_oen <= #1 1'b0; // keep SCL low
                        sda_oen <= #1 1'b0; // set SDA low
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 stop_b:
                    begin
                        c_state <= #1 stop_c;
                        scl_oen <= #1 1'b1; // set SCL high
                        sda_oen <= #1 1'b0; // keep SDA low
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 stop_c:
                    begin
                        c_state <= #1 stop_d;
                        scl_oen <= #1 1'b1; // keep SCL high
                        sda_oen <= #1 1'b0; // keep SDA low
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 stop_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b1; // keep SCL high
                        sda_oen <= #1 1'b1; // set SDA high
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
    
                  // read
                  rd_a:
                    begin
                        c_state <= #1 rd_b;
                        scl_oen <= #1 1'b0; // keep SCL low
                        sda_oen <= #1 1'b1; // tri-state SDA
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 rd_b:
                    begin
                        c_state <= #1 rd_c;
                        scl_oen <= #1 1'b1; // set SCL high
                        sda_oen <= #1 1'b1; // keep SDA tri-stated
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 rd_c:
                    begin
                        c_state <= #1 rd_d;
                        scl_oen <= #1 1'b1; // keep SCL high
                        sda_oen <= #1 1'b1; // keep SDA tri-stated
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
                 rd_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0; // set SCL low
                        sda_oen <= #1 1'b1; // keep SDA tri-stated
                        sda_chk <= #1 1'b0; // don't check SDA output
                    end
    
                 // write
                 wr_a:
                    begin
                        c_state <= #1 wr_b;
                        scl_oen <= #1 1'b0; // keep SCL low
                        sda_oen <= #1 din;  // set SDA
                        sda_chk <= #1 1'b0; // don't check SDA output (SCL low)
                    end
                 wr_b:
                    begin
                        c_state <= #1 wr_c;
                        scl_oen <= #1 1'b1; // set SCL high
                        sda_oen <= #1 din;  // keep SDA
                        sda_chk <= #1 1'b0; // don't check SDA output yet
                        // allow some time for SDA and SCL to settle
                    end
                 wr_c:
                    begin
                        c_state <= #1 wr_d;
                        scl_oen <= #1 1'b1; // keep SCL high
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b1; // check SDA output
                    end
                 wr_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0; // set SCL low
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b0; // don't check SDA output (SCL low)
                    end    
                 endcase
         end
         
//********************assign scl and sda output (always gnd)**********************************//
assign scl_o = 1'b0;
assign sda_o = 1'b0;      

endmodule
